#include <arch.h>
#include <plat.h>
#include <log.h>
#include <errno.h>
#include <arch/arm_mpu.h>
#include "arm_mpu_internal.c"

#define CONFIG_ARM_MPU_REGION_MIN_ALIGN_AND_SIZE    (64)

/*
 * Global status variable holding the number of HW MPU region indices, which
 * have been reserved by the MPU driver to program the static (fixed) memory
 * regions
 */
static uint8_t static_regions_num;
#if 0
/**
 * This internal function is utilized by the MPU driver to combine a given
 * region attribute configuration and size and fill-in a driver-specific
 * structure with the correct MPU region configuration.
 */
static inline void get_region_attr_from_mpu_partition_info(
            arm_mpu_region_attr_t *p_attr,
            const mem_partition_attr_t *attr, uint32_t base, uint32_t size)
{
    (void) base;
    (void) size;

    p_attr->rbar = attr->rbar_attr & 
                (MPU_PRBAR_XN_Msk | MPU_PRBAR_AP_Msk | MPU_PRBAR_SH_Msk);
    p_attr->mair_idx = attr->mair_idx;
}

/* @brief Partition sanity check
 *
 * This internal function performs run-time sanity check for
 * MPU region start address and size.
 *
 * @param part Pointer to the data structure holding the partition
 *        information (must be valid).
 */
static int mpu_partition_is_valid(const mpu_partition_t *part)
{
    /* Partition size must be power-of-two,
     * and greater or equal to the minimum
     * MPU region size. Start address of the
     * partition must align with size.
     */
    int partition_is_valid =
        ((part->size & (part->size - 1U)) == 0U) &&
        (part->size >= CONFIG_ARM_MPU_REGION_MIN_ALIGN_AND_SIZE) &&
        ((part->start & (part->size - 1U)) == 0U);

    return partition_is_valid;
}

static int region_allocate_and_init(const uint8_t index,
            const struct arm_mpu_region *region_conf)
{
    /* Attempt to allocate new region index. */
    if (index > (mpu_get_num_regions() - 1U)) {
         /* No available MPU region index. */
         return -EINVAL;
    }

    /* Configure the region */

    return index;
}

/* This internal function programs an MPU region
 * of a given configuration at a given MPU index.
 */
static int mpu_configure_region(const uint8_t index,
                        const mpu_partition_t *new_region)
{
    struct arm_mpu_region region_conf;

    /* Populate internal ARM MPU region configuration structure. */
    region_conf.base = new_region->start;
    region_conf.limit = (new_region->start + new_region->size - 1) & MPU_PRLAR_LIMIT_Msk;
    get_region_attr_from_mpu_partition_info(&region_conf.attr,
                    &new_region->attr, new_region->start, new_region->size);

    /* Allocate and program region */
    return region_allocate_and_init(index,
                (const struct arm_mpu_region *)&region_conf);
}

/* This internal function programs a set of given MPU regions
 * over a background memory area, optionally performing a
 * sanity check of the memory regions to be programmed.
 */
static int mpu_configure_regions(const mpu_partition_t regions[],
                        uint8_t regions_num, uint8_t start_reg_index,
                        bool do_sanity_check)
{
    int i;
    int reg_index = start_reg_index;

    for (i = 0; i < regions_num; i++) {
        if (regions[i].size == 0U) {
            continue;
        }

        /* Non-empty region. */
        if (do_sanity_check &&
                (!mpu_partition_is_valid(&regions[i]))) {
                ERROR("Partition %u: sanity check failed.", i);
                return -EINVAL;
        }

        reg_index = mpu_configure_region(reg_index, &regions[i]);
        if (reg_index == -EINVAL) {
            return reg_index;
        }

        /* Increment number of programmed MPU indices. */
        reg_index++;
    }

    return reg_index;
}

/*
 * @brief configure a set of fixed (static) MPU regions
 *
 * Internal API function to configure a set of static MPU memory regions,
 * within a (background) memory area determined by start and end address.
 * The total number of HW MPU regions to be programmed depends on the MPU
 * architecture.
 *
 * The function shall be invoked once, upon system initialization.
 * @param static_regions[] an array of pointers to memory partitions
 *                         to be programmed
 * @param regions_num the number of regions to be programmed
 * @param background_area_start the start address of the background memory area
 * @param background_area_end the end address of the background memory area
 *
 * The function shall assert if the operation cannot be not performed
 * successfully. Therefore:
 * - the number of HW MPU regions to be programmed shall not exceed the number
 *   of available MPU indices,
 * - the size and alignment of the static regions shall comply with the
 *   requirements of the MPU hardware.
 */
static int mpu_configure_static_mpu_regions(const mpu_partition_t static_regions[],
                                const uint8_t regions_num,
                                const uint32_t background_area_base,
                                const uint32_t background_area_end)
{
    int mpu_reg_index = static_regions_num;

    mpu_reg_index = mpu_configure_regions(static_regions, regions_num, mpu_reg_index, true);

    static_regions_num = mpu_reg_index;

    return mpu_reg_index;
}

void arm_mpu_configure_static_regions(void)
{
    /* not use for now  */
}
#endif

int arm_mpu_init(void)
{
    uint32_t r_index;

    if (plat_mpu_config.num_regions > mpu_get_num_regions()) {
       /* Attempt to configure more MPU regions than
        * what is supported by hardware
        */
        return -EINVAL;
    }

    mpu_disable();

    plat_mpu_mair_config();

    /* Program fixed regions configured at SOC definition. */
    for (r_index = 0U; r_index < plat_mpu_config.num_regions; r_index++) {
        region_init(r_index, &plat_mpu_config.mpu_regions[r_index]);
    }

    static_regions_num = plat_mpu_config.num_regions;

    mpu_enable();

    return 0;
}


